//
//  OpenGLView.m
//  OpenGLES
//
//  Authors:
//    - 2016.03.01 David Qiu (david@davidqiu.com)
//
//  Copyright (C) 2016 DJI. All rights reserved.
//

#import "OpenGLView.h"
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>



const OpenGLViewVertex Vertices[] = {
    {{ 1, -1, 0}, {1, 0, 0, 1}},
    {{ 1,  1, 0}, {0, 1, 0, 1}},
    {{-1,  1, 0}, {0, 0, 1, 1}},
    {{-1, -1, 0}, {0, 0, 0, 1}}
};

const GLubyte Indices[] = {
    0, 1, 2,
    2, 3, 0
};



/**
 *  OpenGL view. (Internal Interface)
 */
@interface OpenGLView () {
    CAEAGLLayer* _eaglLayer;
    EAGLContext* _context;
    GLuint _colorRenderBuffer;
    
    GLuint _positionSlot;
    GLuint _colorSlot;
}

@end



/**
 *  OpenGL view. (Implementation)
 */
@implementation OpenGLView

#pragma mark - Object Control

- (void) _init {
    // Setup OpenGL components
    [self setupLayer];
    [self setupContext];
    [self setupRenderBuffer];
    [self setupFrameBuffer];
    [self setupVertexBufferObjects];
    
    // Compile OpenGL program
    [self compileProgram];
    
    // Render
    [self render];
}

- (instancetype) init {
    if (self = [super init]) {
        [self _init];
    }
    
    return self;
}

- (instancetype) initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self _init];
    }
    
    return self;
}

- (instancetype) initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self _init];
    }
    
    return self;
}

+ (Class) layerClass {
    return [CAEAGLLayer class];
}


#pragma mark - OpenGL Setup

- (void) setupLayer {
    _eaglLayer = (CAEAGLLayer *) self.layer;
    _eaglLayer.opaque = YES;
}

- (void) setupContext {
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    _context = [[EAGLContext alloc] initWithAPI:api];
    
    if (!_context) {
        NSLog(@"[ ERROR ] Failed to initialize OpenGLES 2.0 context.");
        exit(1);
    }
    
    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"[ ERROR ] Failed to set current OpenGL context.");
        exit(1);
    }
}

- (void)setupRenderBuffer {
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}

- (void)setupFrameBuffer {
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
}

- (void)setupVertexBufferObjects {
    // Setup vertex buffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
    
    // Setup index buffer
    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}


#pragma mark - OpenGL Shader

- (GLuint) compileShader:(NSString *)shaderName withType:(GLenum)shaderType {
    // Retrieve the shader script content
    NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"];
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
    
    // Create shader
    GLuint shaderHandle = glCreateShader(shaderType);
    
    // Bind shader souce
    const char * shaderStringUTF8 = [shaderString UTF8String];
    GLsizei shaderStringLength = (GLsizei)[shaderString length];
    glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
    
    // Compile shader
    glCompileShader(shaderHandle);
    
    // Check compile result
    GLint compileStatus;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileStatus);
    if (compileStatus == GL_FALSE) {
        GLchar message[256];
        glGetShaderInfoLog(shaderHandle, sizeof(message), 0, &message[0]);
        NSString* messageString = [NSString stringWithUTF8String:message];
        NSLog(@"[ ERROR ] Failed to compile shader. (shader: %@, type: %u, error: %@)", shaderName, shaderType, messageString);
        exit(1);
    }
    
    // Return shader handle
    return shaderHandle;
}

- (GLuint) linkProgram {
    // Compile vertex shader
    GLuint vertexShaderHandle = [self compileShader:@"SimpleVertex" withType:GL_VERTEX_SHADER];
    
    // Compile fragment shader
    GLuint fragmentShaderHandle = [self compileShader:@"SimpleFragment" withType:GL_FRAGMENT_SHADER];
    
    // Create program
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShaderHandle);
    glAttachShader(programHandle, fragmentShaderHandle);
    glLinkProgram(programHandle);
    
    // Check link status
    GLint linkStatus;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar message[256];
        glGetProgramInfoLog(programHandle, sizeof(message), 0, &message[0]);
        NSString *messageString = [NSString stringWithUTF8String:message];
        NSLog(@"[ ERROR ] Failed to link the program. (error: %@)", messageString);
        exit(1);
    }
    
    return programHandle;
}

- (void) compileProgram {
    // Link program
    GLuint programHandle = [self linkProgram];
    
    // Use program
    glUseProgram(programHandle);
    
    // Bind slots
    _positionSlot = glGetAttribLocation(programHandle, "position");
    _colorSlot = glGetAttribLocation(programHandle, "sourceColor");
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_colorSlot);
}


#pragma mark - OpenGL Render

- (void) render {
    // Reset canvas clear color
    glClearColor(0/255.0, 0/255.0, 0/255.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    // Set view point
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    // Bind vertext attributes
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(OpenGLViewVertex), (GLvoid *)(sizeof(float) * 0));
    glVertexAttribPointer(_colorSlot,    4, GL_FLOAT, GL_FALSE, sizeof(OpenGLViewVertex), (GLvoid *)(sizeof(float) * 3));
    
    // Draw elements
    glDrawElements(GL_TRIANGLES,     sizeof(Indices)/sizeof(Indices[0]),
                   GL_UNSIGNED_BYTE, 0);
    
    // Present render buffer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}


@end


